#include "PrecompiledHeader.h"
#include "CMaterial.h"

/*--------------------------------------------------------------------------*/
CMaterial* g_Material = NULL;
/*--------------------------------------------------------------------------*/

CMaterial::CMaterial() :
	IEngMod("CMaterials")
{
	this->m_boundMaterial = NULL;

	this->ubo_material = ROpenGL::GetUniformBuffer("ubo_material");
	AssertLogC(this->ubo_material == NULL, "UBO not found!");

	this->ubo_light = ROpenGL::GetUniformBuffer("ubo_light");
	AssertLogC(this->ubo_light == NULL, "UBO not fount!");
}

/*--------------------------------------------------------------------------*/

CMaterial::~CMaterial()
{
	/*--------------------------------------------------------------------------*/

	for (auto iter = this->m_contexts.begin(); iter != this->m_contexts.end(); ++iter)
	{
		if (iter->second)
		{
			delete iter->second;
			iter->second = NULL;
		}
	}

	/*--------------------------------------------------------------------------*/

	for (auto iter = this->m_materials.begin(); iter != this->m_materials.end(); ++iter)
	{
		if (iter->second)
		{
			delete iter->second;
			iter->second = NULL;
		}
	}

	/*--------------------------------------------------------------------------*/
}

/*--------------------------------------------------------------------------*/

void CMaterial::BindMaterial(const Material* rm, EMaterialTechnique::Type technique)
{
	AssertLogC(rm == NULL, "Binding NULL Material!");
	uint changeFlags = 0;

	if (this->m_boundMaterial != rm)
	{
		changeFlags |= EMaterialBindFlag::ChangeMaterial;
		this->m_boundMaterial = rm;
	}

	if (this->m_boundTechnique != technique)
	{
		changeFlags |= EMaterialBindFlag::ChangeTechnique;
		this->m_boundTechnique = technique;
	}

	/*--------------------------------------------------------------------------*/

	const Technique* newTechnique = this->m_boundMaterial->GetTechnique(technique);
	AssertLogC(newTechnique == NULL, "Such technique does not exist in %s!", rm->GetName().c_str());
	this->m_currentTechnique = newTechnique;

	/*--------------------------------------------------------------------------*/

	const Context* newContext = this->m_currentTechnique->m_context;
	AssertLogC(newContext == NULL, "Technique without Context detected in %s!", rm->GetName().c_str());

	if (newContext != this->m_currentContext)
	{
		changeFlags |= EMaterialBindFlag::ChangeContext;
		this->m_currentContext = newContext;
	}

	/*--------------------------------------------------------------------------*/

	const GLProgram* newProgram = this->m_boundMaterial->GetProgram(technique);
	AssertLogC(newTechnique == NULL, "NULL Program in BindMaterial@%s detected", rm->GetName().c_str());

	if (newProgram != this->m_currentProgram)
	{
		changeFlags |= EMaterialBindFlag::ChangeProgram;
		this->m_currentProgram = newProgram;
	}

	/*--------------------------------------------------------------------------*/

	if (changeFlags & EMaterialBindFlag::ChangeContext)
	{
		this->Bind_BindContext();
	}

	if (changeFlags & EMaterialBindFlag::ChangeMaterial)
	{
		this->Bind_MaterialProperties();
		this->Bind_MaterialTextures();
	}

	if (changeFlags & EMaterialBindFlag::ChangeProgram)
	{
		this->Bind_BindProgram();
	}

	this->Bind_Samplers();
}

/*--------------------------------------------------------------------------*/
//
// void CMaterial::ApplyMaterialShader(bool useInstancing)
// {
// 	if (this->m_boundMaterial != this->m_nullMaterial)
// 	{
// 		AssertLogC(this->m_boundMaterial == NULL, "NULL as activeMaterial");
//
// 		Material::Version version = Material::ver_default;
//
// 		if (useInstancing)
// 		{
// 			version = Material::ver_instanced;
// 		}
//
// 		GLProgram* program = this->m_boundMaterial->GetProgram(version);
// 		AssertLogC(program == NULL, "Material does not contain required shader");
//
// 		ROpenGL::BindProgram(program);
//
// 		const std::vector<std::pair<std::string, GLTexture*>>& textures = this->m_boundMaterial->GetTextures();
// 		int texturesCount = textures.size();
//
// 		for (int i = 0; i < texturesCount; ++i)
// 		{
// 			ROpenGL::SetSampler(textures[i].first, textures[i].second);
// 		}
//
// 		ROpenGL::ReloadUBOsOfBoundShader();
// 		ROpenGL::RebindSamplersToBoundShader();
// 	}
// 	else
// 	{
// 		ROpenGL::UnbindProgram();
// 	}
// }

/*--------------------------------------------------------------------------*/

const Material* CMaterial::GetMaterial(const std::string& name) const
{
	auto iter = this->m_materials.find(name);

	if (iter == this->m_materials.end())
	{
		AssertLogC(true, "Material %s not found!", name.c_str());

		if (name != "mat_default")
		{
			return GetMaterial("mat_default");
		}
		else
		{
			return NULL;
		}
	}
	else
	{
		return (iter->second);
	}
}

/*--------------------------------------------------------------------------*/

void CMaterial::RegisterMaterial(const std::string& name, Material* material)
{
	const Material* createdMaterial = GetMaterial(name);

	if (createdMaterial)
	{
		AssertLogC(createdMaterial != NULL, "Material %s already exists!", name.c_str());
	}
	else
	{
		this->m_materials[name] = material;
	}
}

/*--------------------------------------------------------------------------*/

const MaterialPtnMap& CMaterial::GetMaterials() const
{
	return this->m_materials;
}

/*--------------------------------------------------------------------------*/

void CMaterial::LoadMaterials(const std::string& file)
{
	pugi::xml_document mfile;
	AssertLogC(mfile.load_file(file.c_str()) == false, "File %s not found!", file.c_str());

	pugi::xml_node materials = mfile.child("materials");

	for (pugi::xml_node material = materials.child("material"); material; material = material.next_sibling("material"))
	{
		std::string name = material.attribute("name").as_string("undefined");
		std::string matfile = material.attribute("file").as_string("undefined");

		auto iter = this->m_materials.find(name);

		if (iter != this->m_materials.end())
		{
			AssertLogC(true, "Trying to overwrite existing material in file %s, material %s", file.c_str(), name.c_str());
			continue;
		}

		this->m_materials[name] = new Material(matfile);
	}
}

/*--------------------------------------------------------------------------*/

const Material* CMaterial::GetBoundMaterial() const
{
	return this->m_boundMaterial;
}

/*--------------------------------------------------------------------------*/

EMaterialTechnique::Type CMaterial::GetBoundTechnique() const
{
	return this->m_boundTechnique;
}

/*--------------------------------------------------------------------------*/

void CMaterial::SetLightProperties(const glm::vec3* position, const glm::vec3* diffuse, float* specular, const float* radius, const float* angleCos, const glm::vec3* direction)
{
	if (position)
	{
		ROpenGL::SetUniformBufferValue(this->ubo_light, EUboLight::LightPosition, (void*) position);
	}

	if (diffuse)
	{
		ROpenGL::SetUniformBufferValue(this->ubo_light, EUboLight::LightDiffuse, (void*) diffuse);
	}

	if (specular)
	{
		ROpenGL::SetUniformBufferValue(this->ubo_light, EUboLight::LightSpecularShininess, (void*) specular);
	}

	if (radius)
	{
		ROpenGL::SetUniformBufferValue(this->ubo_light, EUboLight::LightRadius,	(void*) radius);
	}

	if (angleCos)
	{
		ROpenGL::SetUniformBufferValue(this->ubo_light, EUboLight::LightAngleCos, (void*) angleCos);
	}

	if (direction)
	{
		ROpenGL::SetUniformBufferValue(this->ubo_light, EUboLight::LightDirection, (void*) direction);
	}
}

/*--------------------------------------------------------------------------*/

void CMaterial::SetMaterialProperties(const glm::vec3* diffuse, const float* shininess, const float* normalMapping)
{
	if (diffuse)
	{
		ROpenGL::SetUniformBufferValue(this->ubo_material, EUboMaterial::MaterialDiffuse, (void*) diffuse);
	}

	if (shininess)
	{
		ROpenGL::SetUniformBufferValue(this->ubo_material, EUboMaterial::MaterialShininess, (void*) shininess);
	}

	if (normalMapping)
	{
		ROpenGL::SetUniformBufferValue(this->ubo_material, EUboMaterial::MaterialNormalMap, (void*) normalMapping);
	}
}

/*--------------------------------------------------------------------------*/

Context* CMaterial::CreateNewContext(const std::string& name)
{
	AssertLogC(name.empty(), "Name can not be empty!");

	auto iter = this->m_contexts.find(name);

	if (iter != this->m_contexts.end())
	{
		AssertLogC(true, "Duplicating context %s", name.c_str());
		return NULL;
	}
	else
	{
		Context* context = new Context(name);
		this->m_contexts[name] = context;
		return context;
	}
}

/*--------------------------------------------------------------------------*/

const Context* CMaterial::GetContext(const std::string& name) const
{
	if (name.empty())
	{
		return NULL;
	}

	auto iter = this->m_contexts.find(name);

	if (iter != this->m_contexts.end())
	{
		return iter->second;
	}
	else
	{
		return NULL;
	}
}

/*--------------------------------------------------------------------------*/

void CMaterial::Bind_BindContext()
{
	uint flags = this->m_currentContext->m_flags;

	ROpenGL::ColorMask(flags & EContextFlag::WriteColor);
	ROpenGL::DepthMask(flags & EContextFlag::WriteDepth);
	// todo ROpenGL::StencilMask(flags & EContextFlag::WriteStencil);

	ROpenGL::PolygonMode(this->m_currentContext->m_frontPolygonMode, this->m_currentContext->m_backPolygonMode);

	ROpenGL::BlendEnabled(flags & EContextFlag::Blending);
	ROpenGL::BlendFunc(this->m_currentContext->m_blendSrc, this->m_currentContext->m_blendDst);

	ROpenGL::CullFaceEnabled(flags & EContextFlag::Culling);
	ROpenGL::CullFaceMode(this->m_currentContext->m_cullingMode);

	ROpenGL::DepthTest(flags & EContextFlag::DepthTesting);
}

/*--------------------------------------------------------------------------*/

void CMaterial::Bind_BindProgram()
{
	ROpenGL::BindProgram(const_cast<GLProgram*>(this->m_currentProgram));
}

/*--------------------------------------------------------------------------*/

void CMaterial::Bind_MaterialTextures()
{
	auto& textures = this->m_boundMaterial->GetTextures();
	int texturesCount = textures.size();

	for (auto iter = textures.begin(); iter != textures.end(); ++iter)
	{
		ROpenGL::SetSampler(iter->first, iter->second);
	}
}

/*--------------------------------------------------------------------------*/

void CMaterial::Bind_MaterialProperties()
{
	this->SetMaterialProperties(&this->m_boundMaterial->GetDiffuseColor(),
	                            &this->m_boundMaterial->GetSpecularShininess(),
	                            &this->m_boundMaterial->GetNormalMapStrength());
}

/*--------------------------------------------------------------------------*/

void CMaterial::Bind_Samplers()
{
	for (auto iter = this->m_currentTechnique->m_samplers.begin(); iter != this->m_currentTechnique->m_samplers.end(); ++iter)
	{
		if (iter->samplerPtn == NULL)
		{
			//todo
		}

		if (iter->texturePtn == NULL)
		{
			const_cast<GLTexture*>(iter->texturePtn) = ROpenGL::GetTexture(iter->textureName);
		}

		ROpenGL::SetSampler(iter->name, iter->texturePtn);
	}
}

/*--------------------------------------------------------------------------*/
